﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Core;
#elif __ANDROID__
using Android.Runtime;
#endif

namespace UnitDriven
{
	public class TestContext : ObservableObject, IRunTests, IDisposable
	{
		private TestGroup _root;
		private int _total;
		private bool _isRunning;
		private string _filter;
		private StringBuilder globalErrors = new StringBuilder();

		public string Filter
		{
			get { return _filter; }
			set
			{
				_filter = value;
				this.CalculateTotal();
				OnPropertyChanged("Filter");
			}
		}

		public bool IsRunning
		{
			get { return _isRunning; }
			private set
			{
				_isRunning = value;
				OnPropertyChanged("IsRunning");
				OnPropertyChanged("IsNotRunning");
			}
		}

		public bool IsNotRunning
		{
			get { return !IsRunning; }
			set { IsRunning = !value; }
		}

		public int Total
		{
			get { return _total; }
			set
			{
				_total = value;
				OnPropertyChanged("Total");
				OnPropertyChanged("Succeeded");
			}
		}

		public int Succeeded
		{
			get
			{
				var count = (from t in Items
							 where t.IsVisible == Visibility.Visible
							 select t.Succeeded)
							 .Sum();

				return count;
			}
		}

		public bool HasError
		{
			get { return this.globalErrors.Length > 0; }
		}

		public Visibility IsVisible
		{
			get { return Visibility.Visible; }
		}

		public TestContext(Assembly assembly)
		{
			if (assembly == null)
				throw new ArgumentNullException("assembly");

			_root = new TestGroup(this, "root");
#if NETFX_CORE
            foreach(var t in from type in assembly.DefinedTypes
#else
			foreach (Type t in from type in assembly.GetTypes()
#endif
							   orderby type.FullName ascending
							   select type)
			{
				if (t.IsPublic && !t.IsInterface && !t.IsAbstract && t.IsDefined(typeof(TestClassAttribute), true))
				{
					TestGroup group = ConstructTree(this, _root, t.Namespace);

#if NETFX_CORE
                    TypeTester tester = new TypeTester(this, t.AsType());
#else
					TypeTester tester = new TypeTester(this, t);
#endif
					_total += tester.Items.Count;
					group.Items.Add(tester);
				}
			}

			this.CalculateTotal();

#if __ANDROID__
	        Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += Current_UnhandledException;
#else
			Application.Current.UnhandledException += Current_UnhandledException;
#endif
			_root.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(RootPropertyChanged);
		}

		private void CalculateTotal()
		{
			var total = CalculateTotal(_root);
			this.Total = total;
		}

		private static int CalculateTotal(TestGroup group)
		{
			var total = 0;
			foreach (var item in group.Items)
			{
				var typeTester = item as TypeTester;
				if (typeTester != null)
				{
					total += typeTester.Items.Where(i => i.IsVisible == Visibility.Visible).Count();
					continue;
				}

				total += CalculateTotal((TestGroup)item);
			}

			return total;
		}

#if NETFX_CORE
        private async void Current_UnhandledException(object sender, UnhandledExceptionEventArgs e)
#elif __ANDROID__
	    private void Current_UnhandledException(object sender, RaiseThrowableEventArgs e)
#else
		private void Current_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
#endif
		{
			e.Handled = true;

			if (globalErrors.Length > 0)
				globalErrors.AppendLine();

#if __ANDROID__
	        globalErrors.AppendLine(e.Exception.ToDisplay());

	        OnPropertyChanged("HasError");
	        OnPropertyChanged("Message");
#elif NETFX_CORE
            globalErrors.AppendLine(e.Exception.ToDisplay());
            await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                OnPropertyChanged("HasError");
                OnPropertyChanged("Message");
            });
#else
			globalErrors.AppendLine(e.ExceptionObject.ToDisplay());

			this.Dispatcher.BeginInvoke(() =>
			{
				OnPropertyChanged("HasError");
				OnPropertyChanged("Message");
			});
#endif
		}

		private static TestGroup ConstructTree(TestContext context, TestGroup parent, string name)
		{
			var group = (from item in parent.Items
						 where item.Name == name
						 select item)
						.Cast<TestGroup>()
						.FirstOrDefault();

			if (group == null)
			{
				group = new TestGroup(context, name);
				parent.Items.Add(group);
			}

			return group;
		}

		private void RootPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			if (e.PropertyName == "IsRunning")
			{
				IsRunning = Items.Any(t => t.IsRunning);
				OnPropertyChanged("Succeeded");
			}
			else
			{
				OnPropertyChanged(e.PropertyName);
			}
		}

		public string Message
		{
			get { return this.globalErrors.ToString(); }
		}

		public IEnumerable<IRunTests> Items
		{
			get { return _root.Items; }
		}

		public void Run()
		{
			this.globalErrors = new StringBuilder();
			OnPropertyChanged("HasError");
			OnPropertyChanged("Message");

			IsRunning = true;
			foreach (IRunTests tester in Items)
				tester.RunTests();
		}

		public void Dispose()
		{
#if __ANDROID__
	  Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser -= Current_UnhandledException;
#else
			Application.Current.UnhandledException -= Current_UnhandledException;
#endif
		}

		#region IRunTests Members

		string IRunTests.Name
		{
			get { return "Test Context"; }
		}

		TestResult IRunTests.Status
		{
			get { return this._root.Status; }
		}

		void IRunTests.RunTests()
		{
			this.Run();
		}

		#endregion
	}
}
